// Tencent is pleased to support the open source community by making
// 蓝鲸智云 - 监控平台/日志平台 (BlueKing - Monitor/Log) available.
// Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. All rights reserved.
// Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://opensource.org/licenses/MIT
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
//

package converter

import (
	"github.com/elastic/beats/libbeat/common"
	"go.opentelemetry.io/collector/model/pdata"

	"github.com/TencentBlueKing/bk-collector/define"
)

type tracesEvent struct {
	define.CommonEvent
}

func (e tracesEvent) RecordType() define.RecordType { return define.RecordTraces }

var TracesConverter EventConverter = tracesConverter{}

type tracesConverter struct{}

func (c tracesConverter) ToEvent(dataId int32, data common.MapStr) define.Event {
	return tracesEvent{define.NewCommonEvent(dataId, data)}
}

func (c tracesConverter) ToDataID(record *define.Record) int32 {
	return record.Token.TracesDataId
}

func (c tracesConverter) Convert(record *define.Record) define.Events {
	var events define.Events
	pdTraces, ok := record.Data.(pdata.Traces)
	if !ok {
		return events
	}
	resourceSpans := pdTraces.ResourceSpans()
	if resourceSpans.Len() == 0 {
		return nil
	}
	dataId := c.ToDataID(record)

	for i := 0; i < resourceSpans.Len(); i++ {
		resourceSpan := resourceSpans.At(i)
		resources := resourceSpan.Resource().Attributes().AsRaw()
		instSpans := resourceSpan.InstrumentationLibrarySpans()
		for j := 0; j < instSpans.Len(); j++ {
			instSpan := instSpans.At(j).Spans()
			for k := 0; k < instSpan.Len(); k++ {
				content := c.Extract(record.RequestClient.IP, instSpan.At(k), resources)
				events = append(events, c.ToEvent(dataId, content))
			}
		}
	}

	return events
}

func (c tracesConverter) Extract(ip string, pdSpan pdata.Span, resources common.MapStr) common.MapStr {
	m := common.MapStr{
		"span_name":      pdSpan.Name(),
		"span_id":        pdSpan.SpanID().HexString(),
		"trace_id":       pdSpan.TraceID().HexString(),
		"parent_span_id": pdSpan.ParentSpanID().HexString(),
		"kind":           pdSpan.Kind(),
		"start_time":     pdSpan.StartTimestamp() / 1000,
		"end_time":       pdSpan.EndTimestamp() / 1000,
		"trace_state":    pdSpan.TraceState(),
		"elapsed_time":   c.spanElapsedTime(pdSpan.EndTimestamp(), pdSpan.StartTimestamp()),
		"links":          c.spanLinks(pdSpan.Links()),
		"events":         c.spanEvents(pdSpan.Events()),
		"status":         c.spanStatus(pdSpan.Status()),
		"attributes":     pdSpan.Attributes().AsRaw(),
		"resource":       resources,
		"client_ip":      ip,
	}
	return m
}

func (c tracesConverter) spanElapsedTime(endTs, startTs pdata.Timestamp) pdata.Timestamp {
	return (endTs - startTs) / 1000
}

func (c tracesConverter) spanLinks(links pdata.SpanLinkSlice) []common.MapStr {
	result := make([]common.MapStr, 0, links.Len())
	for i := 0; i < links.Len(); i++ {
		link := links.At(i)
		result = append(result, common.MapStr{
			"trace_id":    link.TraceID().HexString(),
			"span_id":     link.SpanID().HexString(),
			"trace_state": link.TraceState(),
			"attributes":  link.Attributes().AsRaw(),
		})
	}
	return result
}

func (c tracesConverter) spanStatus(status pdata.SpanStatus) common.MapStr {
	return common.MapStr{
		"code":    status.Code(),
		"message": status.Message(),
	}
}

func (c tracesConverter) spanEvents(events pdata.SpanEventSlice) []common.MapStr {
	result := make([]common.MapStr, 0, events.Len())
	for i := 0; i < events.Len(); i++ {
		event := events.At(i)
		result = append(result, common.MapStr{
			"name":       event.Name(),
			"timestamp":  event.Timestamp() / 1000,
			"attributes": event.Attributes().AsRaw(),
		})
	}
	return result
}
